/*
  AdViEmulator - AdventureVision emulator
  Copyright (C) 2012-2013  JustBurn

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPainter>
#include <QKeyEvent>
#include "cpuviewbox.h"

CPUViewBox::CPUViewBox(QWidget *parent) :
	QWidget(parent)
{
	avemu = 0;
	topAddr = 0;
	topAddrLast = 0;
	maxAddr = 4096;
	curAddr = 0;
	resize(size());
	scrollProc = true;
}

void CPUViewBox::attachEmu(AdViEmulator *emu, QScrollBar *sb)
{
	avemu = emu;
	viewsb = sb;
	connect(sb, SIGNAL(valueChanged(int)), this, SLOT(scrollChanged(int)));
}

void CPUViewBox::setTopAddress(int value)
{
	if (value < 0) value = 0;
	if (value >= topAddrLast) value = topAddrLast;
	topAddr = value;
	scrollProc = false;
	viewsb->setValue(topAddr);
	scrollProc = true;
	repaint();
}

void CPUViewBox::clipAddress(int addr)
{
	if (addr < topAddr) topAddr = addr;
	if (addr >= addressesCache.at(numLines)) topAddr = addr - numLines + 1;
	scrollProc = false;
	viewsb->setValue(topAddr);
	scrollProc = true;
}

void CPUViewBox::moveCursor(int addr, bool refresh)
{
	if (addr < 0) addr = 0;
	if (addr >= maxAddr) addr = maxAddr - 1;
	curAddr = addr;
	clipAddress(curAddr);
	if (refresh) repaint();
}

void CPUViewBox::paintEvent(QPaintEvent *)
{
	QPainter painter(this);
	QBrush cursor(hasFocus() ? QColor::fromRgb(255, 160, 64) : QColor::fromRgb(160, 160, 160), Qt::Dense4Pattern);

	// Draw white background
	painter.setBrush(Qt::white);
	painter.setPen(Qt::transparent);
	painter.drawRect(0, 0, width(), height());
	painter.setPen(Qt::black);

	// Get metrics
	int twidth = painter.fontMetrics().width('0');
	int theight = painter.fontMetrics().height();

	// Draw instructions
	int addr = topAddr;
	int laddr = addr-1;
	int y = 0;
	for (; y<numLines + 1; y++) {
		if (addr >= maxAddr) break;
		int ypos = (y + 1) * theight;

		// Draw breakpoint
		bool bp = false;
		if (avemu->ReadROMType(addr) == AdViEmulator::CPU_BIOS) {
			if (avemu->BIOS_Trap[addr & 1023]) bp = true;
		} else {
			if (avemu->ROM_Trap[addr & 4095]) bp = true;
		}
		if (bp) {
			painter.setBrush(Qt::red);
			painter.setPen(Qt::transparent);
			painter.drawRect(0, y * theight + 3, width(), theight);
			painter.setPen(Qt::black);
		}

		// Draw cursor
		if ((curAddr > laddr) && (curAddr <= addr)) {
			painter.setBrush(cursor);
			painter.setPen(Qt::transparent);
			if (curAddr == addr) {
				painter.drawRect(0, y * theight + 3, width(), theight);
			} else {
				painter.drawRect(0, y * theight + 3 - theight / 2, width(), theight);
			}
			painter.setPen(Qt::black);
		}

		// Draw PC cursor
		if ((avemu->cpu.pc.w > laddr) && (avemu->cpu.pc.w <= addr)) {
			painter.setPen(Qt::black);
			if (avemu->cpu.pc.w == addr) {
				painter.drawText(5 + twidth * 12, ypos, ">");
				painter.drawText(8 + twidth * 12, ypos, ">");
			} else {
				painter.drawText(5 + twidth * 12, ypos - theight / 2, ">");
				painter.drawText(8 + twidth * 12, ypos - theight / 2, ">");
			}
		}
		laddr = addr;

		// Write content
		QString addrstr = QString::number(addr, 16).toUpper();
		addrstr = QString().fill('0', 3 - addrstr.length()) + addrstr;
		if (avemu->IsBIOS(addr)) {
			painter.drawText(5, ypos, "B@" + addrstr + ":");
		} else {
			painter.drawText(5, ypos, "R@" + addrstr + ":");
		}
		int iraddr = addr;
		int opSize = avemu->cpu.GetInstructionSize(iraddr);
		addressesCache[y] = addr;
		for (int x=0; x<opSize; x++) {
			quint8 data = avemu->ReadROMData(addr);
			QString datastr = QString::number(data, 16).toUpper();
			datastr = QString().fill('0', 2 - datastr.length()) + datastr;
			painter.drawText(5 + twidth * (6 + x * 3), ypos, datastr);
			if (!((opSize > 1) && !(laddr & 0x800) && (addr & 0x800))) addr++;
			if (addr >= maxAddr) break;
		}
		char Instructi[64];
		avemu->cpu.GetInstructionName(iraddr, Instructi);
		painter.drawText(5 + twidth * 14, ypos, Instructi);
	}
	for (; y<numLines + 1; y++) {
		addressesCache[y] = 0xFFF;
	}
}

void CPUViewBox::resizeEvent(QResizeEvent *event)
{
	numLines = event->size().height() / this->fontMetrics().height();
	addressesCache.resize(numLines + 1);
	topAddrLast = maxAddr - numLines;
	if (topAddrLast < 0) topAddrLast = 0;
	viewsb->setMaximum(maxAddr - numLines);
	viewsb->setMinimum(0);
}

void CPUViewBox::keyPressEvent(QKeyEvent *event)
{
	switch (event->key()) {
	case Qt::Key_Up:
		if (avemu->cpu.GetInstructionSize(curAddr - 2) == 2) {
			moveCursor(curAddr - 2);
		} else {
			moveCursor(curAddr - 1);
		}
		break;
	case Qt::Key_Down:
		moveCursor(curAddr + avemu->cpu.GetInstructionSize(curAddr));
		break;
	case Qt::Key_PageUp:
		{
			for (int i=0; i<numLines/2; i++) {
				if (avemu->cpu.GetInstructionSize(curAddr - 2) == 2) {
					moveCursor(curAddr - 2);
				} else {
					moveCursor(curAddr - 1);
				}
			}
		} break;
	case Qt::Key_PageDown:
		{
			for (int i=0; i<numLines/2; i++) {
				moveCursor(curAddr + avemu->cpu.GetInstructionSize(curAddr));
			}
		} break;
	case Qt::Key_Home:
		moveCursor(0);
		break;
	case Qt::Key_End:
		moveCursor(maxAddr - 1);
		break;
	case Qt::Key_F6:
		if (avemu->IsBIOS(curAddr)) {
			avemu->SetBreak(AdViEmulator::CPU_BIOS, curAddr, avemu->GetBreak(AdViEmulator::CPU_BIOS, curAddr) ? 0 : AdViEmulator::TRAP_READ);
		} else {
			avemu->SetBreak(AdViEmulator::CPU_ROM, curAddr, avemu->GetBreak(AdViEmulator::CPU_ROM, curAddr) ? 0 : AdViEmulator::TRAP_READ);
		}
		break;
	}
	repaint();
}

void CPUViewBox::wheelEvent(QWheelEvent *event)
{
	if (event->delta() > 0) {
		setTopAddress(topAddr - numLines/2);
	} else if (event->delta() < 0) {
		setTopAddress(topAddr + numLines/2);
	}
}

void CPUViewBox::mousePressEvent(QMouseEvent *event)
{
	int mcy = (event->pos().y() - 3) / this->fontMetrics().height();
	if ((mcy >= 0) && (mcy < numLines)) {
		moveCursor(addressesCache[mcy], true);
		if (event->button() == Qt::MiddleButton) {
			int addr = addressesCache[mcy];
			if (avemu->IsBIOS(addr)) {
				avemu->SetBreak(AdViEmulator::CPU_BIOS, addr, avemu->GetBreak(AdViEmulator::CPU_BIOS, addr) ? 0 : AdViEmulator::TRAP_READ);
			} else {
				avemu->SetBreak(AdViEmulator::CPU_ROM, addr, avemu->GetBreak(AdViEmulator::CPU_ROM, addr) ? 0 : AdViEmulator::TRAP_READ);
			}
			repaint();
		}
	}
}

void CPUViewBox::mouseDoubleClickEvent(QMouseEvent *event)
{
	int mcy = (event->pos().y() - 3) / this->fontMetrics().height();
	if ((mcy >= 0) && (mcy < numLines)) {
		moveCursor(addressesCache[mcy], true);
		if (event->button() == Qt::LeftButton) {
			int addr = addressesCache[mcy];
			if (avemu->IsBIOS(addr)) {
				avemu->SetBreak(AdViEmulator::CPU_BIOS, addr, avemu->GetBreak(AdViEmulator::CPU_BIOS, addr) ? 0 : AdViEmulator::TRAP_READ);
			} else {
				avemu->SetBreak(AdViEmulator::CPU_ROM, addr, avemu->GetBreak(AdViEmulator::CPU_ROM, addr) ? 0 : AdViEmulator::TRAP_READ);
			}
			repaint();
		}
	}
}

void CPUViewBox::scrollChanged(int value)
{
	if (scrollProc) setTopAddress(value);
}
